import org.bytedeco.javacv.OpenCVFrameConverter;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
import javax.imageio.ImageIO;
import static org.bytedeco.javacpp.opencv_core.*;



public class SobelEdgeDetect {
    int width;//图像宽
    int height;//图像高
    int[] grayData;//图像灰度值
    int size;  //图像大小
    int gradientThreshold = -1;//判断时用到的阈值
    BufferedImage outBinary;//输出的边缘图像

    int[][] kernelX = {
            {-1,0,1},
            {-2,0,2},
            {-1,0,1}
    };

    int[][] kernelY = {
            {-1,-2,-1},
            {0,0,0},
            {1,2,1}
    };

    public SobelEdgeDetect(int threshold) {
        gradientThreshold = threshold;
    }


    public void readImage(String imageName) throws IOException {
        File imageFile = new File(imageName);
        BufferedImage bufferedImage = ImageIO.read(imageFile);
        writeImage(bufferedImage,"sss.jpg");
        width = bufferedImage.getWidth();
        height = bufferedImage.getHeight();
        size = width * height;
        int imageData[] = bufferedImage.getRGB(0, 0, width, height, null, 0, width);// 读进的图像的RGB值
        outBinary = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);// 生成边缘图像
        grayData = new int[width * height];// 开辟内存空间
        for (int i = 0; i < imageData.length; i++) {
            double r = (imageData[i] & 0xff0000) >> 16;
            double g = (imageData[i] & 0xff00) >> 8;
            double b = (imageData[i] & 0xff);
            double avg = (r * 0.3 + g * 0.59 + b * 0.11);
            grayData[i] = (int)avg;// 由于读的是灰度图，故只考虑一个分量（三分量值相同）
        }

    }

//    public void createEdgeImage(String desImageName) {
//        float[] gradient = gradientM();// 计算图像各像素点的梯度值
//        float maxGradient = gradient[0];
//        for (int i = 1; i < gradient.length; ++i)
//            if (gradient[i] > maxGradient)
//                maxGradient = gradient[i];// 获取梯度最大值
//        float scaleFactor = 255.0f / maxGradient;// 比例因子用于调整梯度大小
//        if (gradientThreshold >= 0) {
//            for (int y = 1; y < height - 1; ++y)
//                for (int x = 1; x < width - 1; ++x)
//                    if (Math.round(scaleFactor * gradient[y * width + x]) >= gradientThreshold)
//                        outBinary.setRGB(x, y, 0xffffff);// 白色
//        }// 对梯度大小进行阈值处理
//        else {
//            for (int y = 1; y < height - 1; ++y)
//                for (int x = 1; x < width - 1; ++x)
//                    outBinary.setRGB(x, y, 0x000000);// 黑色;
//        }// //不对梯度大小进行阈值处理, 直接给出用比例因子调整后的值
//        writeImage(outBinary, desImageName);
//    }

    public BufferedImage createEdgeImage(String desImageName) {
        float[] gradient = gradientM();// 计算图像各像素点的梯度值
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                int r = (int) gradient[y * width + x];
                int g = r;
                int b = r;
                int rgb =  (0xff << 24) | (r << 16) | (g << 8) | b ;
                outBinary.setRGB(x, y, rgb);
            }
        }
        writeImage(outBinary, desImageName);
        return outBinary;
    }

    public int[] getNumberArea(int[] socelData,String OrginalImage) throws IOException {
        File orginalFile = new File(OrginalImage);
        BufferedImage orginalImage = ImageIO.read(orginalFile);
        width = orginalImage.getWidth();
        height = orginalImage.getHeight();
        size = width * height;
        int imageData[] = orginalImage.getRGB(0, 0, width, height, null, 0, width);// 读进的图像的RGB值
        int[]  grayData = new int[this.width * this.height];
        for (int i = 0; i < imageData.length; i++) {
            double r = (imageData[i] & 0xff0000) >> 16;
            double g = (imageData[i] & 0xff00) >> 8;
            double b = (imageData[i] & 0xff);
            double avg = (r * 0.3 + g * 0.59 + b * 0.11);
            grayData[i] = (int)avg;// 由于读的是灰度图，故只考虑一个分量（三分量值相同）
        }
        BufferedImage outImage;
        HashMap BFSearchResult = BFSearch(socelData);
        int seqSobel = (int)BFSearchResult.get("seq");
        int[] clusterSobel = (int[])BFSearchResult.get("cluster");
        dotSets setsSobel = genSets(clusterSobel,seqSobel,width,height);
        setsSobel.filterSobelSimple();
        if(setsSobel.getLength() <= 2){
            if(setsSobel.getLength() <=1){
                System.out.println("Unable to parse Image!");
                return null;
            }
            System.out.println("finish filter sobel");
        }else{
            System.out.println("continue filter sobel");
            setsSobel.SelectAreaSobel();
        }

        ArrayList digialArea = setsSobel.data.get(1).getArea();
        clusterSobel = setsSobel.fillSobel();
        int image_count = 0;
        for(int c = 0; c < clusterSobel.length;c++)
        {
            if(clusterSobel[c] == 1){
                image_count++;
                System.out.println("1 "+ c);
            }
        }
        int[] mask = setsSobel.getMask(clusterSobel);
        maskImage(grayData,mask,width,height);

        System.out.printf("image_count = %d",image_count);
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                outBinary.setRGB(x, y, grayData [x + y*width]);
            }
        }
        BufferedImage otsuImage = new BufferedImage(outBinary.getWidth(),outBinary.getHeight(),outBinary.getType());
        imageThreshold imageThreshold = new imageThreshold();

        writeImage(outBinary, "test123.jpg");
        writeImage(imageThreshold.filter(outBinary,otsuImage),"otus.jpg");

        return grayData ;
    }

    //得到点(x,y)处的灰度值
    public int getGrayPoint(int x, int y) {
        int ids = y * width + x;
        if(ids < 0 || ids >= this.width*this.height){
            return 100000;
        }else{
            return grayData[y * width + x];
        }

    }
    int count = 0;
    //算子计算 图像每个像素点 的 梯度大小
    protected float[] gradientM() {
        int[] m_pHistogram = new int[256];
        float[] mag = new float[size];
        int gx, gy;
        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x) {
                gx = GradientX(x, y);
                gy = GradientY(x, y);

                //用公式 g=|gx|+|gy|计算图像每个像素点的梯度大小.原因是避免平方和开方耗费大量时间
                //mag[y * width + x] = (float) (Math.abs(gx) + Math.abs(gy));
                if(Math.abs(gy) > 10000 || Math.abs(gy) > 10000)
                {
                    mag[y * width + x] = 0;
                }else {
                    float mags = (float) Math.sqrt((gx * gx) + (gy * gy));
                    if(mags > 255) {
                        mag[y * width + x] = 255;
                    }else{
                        mag[y * width + x] = mags;
                    }
                }
            }
        return mag;
    }



    //算子 计算 点(x,y)处的x方向梯度大小
    protected final int GradientX(int x, int y) {

        int pixelX = (
                (kernelX[0][0] * getGrayPoint(x - 1, y - 1)) +
                        (kernelX[0][1] * getGrayPoint(x, y - 1)) +
                        (kernelX[0][2] * getGrayPoint(x + 1, y - 1)) +
                        (kernelX[1][0] * getGrayPoint(x - 1, y)) +
                        (kernelX[1][1] * getGrayPoint(x, y)) +
                        (kernelX[1][2] * getGrayPoint(x + 1, y)) +
                        (kernelX[2][0] * getGrayPoint(x - 1, y + 1)) +
                        (kernelX[2][1] * getGrayPoint(x, y + 1)) +
                        (kernelX[2][2] * getGrayPoint(x + 1, y + 1))
        );

        return pixelX;

        //return getGrayPoint(x - 1, y - 1) + 2*getGrayPoint(x - 1, y) + getGrayPoint(x - 1, y + 1)
        //- getGrayPoint(x + 1, y - 1) - 2*getGrayPoint(x + 1, y) - getGrayPoint(x + 1, y + 1);
    }// 计算像素点(x,y)X方向上的梯度值
    // 算子 计算 点(x,y)处的y方向梯度大小

    protected final int GradientY(int x, int y) {

        int pixelY = (
                (kernelX[0][0] * getGrayPoint(x - 1, y - 1)) +
                        (kernelY[0][1] * getGrayPoint(x, y - 1)) +
                        (kernelY[0][2] * getGrayPoint(x + 1, y - 1)) +
                        (kernelY[1][0] * getGrayPoint(x - 1, y)) +
                        (kernelY[1][1] * getGrayPoint(x, y)) +
                        (kernelY[1][2] * getGrayPoint(x + 1, y)) +
                        (kernelY[2][0] * getGrayPoint(x - 1, y + 1)) +
                        (kernelY[2][1] * getGrayPoint(x, y + 1)) +
                        (kernelY[2][2] * getGrayPoint(x + 1, y + 1))
        );

        return pixelY;

            /*return getGrayPoint(x - 1, y - 1) + 2*getGrayPoint(x, y - 1)
                    + getGrayPoint(x + 1, y - 1) - getGrayPoint(x - 1, y + 1)
                    - 2*getGrayPoint(x, y + 1) - getGrayPoint(x + 1, y + 1);*/
    }// 计算像素点(x,y)Y方向上的梯度值



    public void writeImage(BufferedImage bi, String imageName) {
        File skinImageOut = new File(imageName);
        try {
            ImageIO.write(bi, "jpg", skinImageOut);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //深度优先搜索
    public HashMap BFSearch(int[] socelData) {
        int width = this.width;
        int height = this.height;
        HashMap matrix = new HashMap();
        HashMap sizemap = new HashMap();
        sizemap.put("width", width);
        sizemap.put("height", height);
        matrix.put("size", sizemap);
        int[] data = new int[width * height];
        matrix.put("data", data);
        //int imageData[] = socelData.getRGB(0, 0, width, height, null, 0, width);// 读进的图像的RGB值
        int imageData[] = socelData;
        boolean[] flags = new boolean[width * height];
        int[] cluster = new int[width * height];
        int i, j, ids;
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                ids = j + i * width;
                int point = (imageData[ids] & 0xff0000) >> 16;
                data[ids] = (point == 0) ? 1 : 0;
                matrix.put("data", data);
                cluster[ids] = data[ids];
                flags[ids] = false;
            }
        }

        int seq = 0;
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        //ArrayList queue = new ArrayList();
        Queue queue = new LinkedList<>();
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                ids = j + i * width;
                if (data[ids] == 1 && !flags[ids]) {
                    seq++;
                    flags[ids] = true;
                    cluster[ids] = seq;
                    int[] x_y = {i, j};
                    queue.add(x_y);
                    while (queue.size() != 0) {
                        int k, nj, ni, nids;
                        int[] pot = (int[]) queue.poll();
                        for (k = 0; k < 4; k++) {
                            ni = pot[0] + dirs[k][0];
                            nj = pot[1] + dirs[k][1];
                            nids = nj + ni * width;
                            if ((ni < height) && (ni >= 0) && (nj < width) && (nj >= 0) && (data[nids] == 1) && (!flags[nids])) {
                                flags[nids] = true;
                                cluster[nids] = seq;
                                int[] x_y2 = {ni, nj};
                                queue.add(x_y2);
                            }
                        }
                    }
                }
            }
        }

        HashMap result = new HashMap();
        result.put("cluster", cluster);
        result.put("seq", seq);

        return result;
    }

    public dotSets genSets(int[] cluster, int seq, int width, int height) {
        dotSets sets = new dotSets(seq,width,height);
        sets.init();
        int i, j, ids;
        dataSet ds;
        for (i = 0; i < height; i++) {
            for (j = 0; j < width; j++) {
                ids = j + i * width;
                ds = sets.data.get(cluster[ids]);
                int[] w_h = {j,i};
                ds.data.add(w_h);//i是高，j是宽
            }
        }
        sets.flush();
        return sets;
    }

    public int[] maskImage(int[] imagedata,int[] mask,int width,int height){
        int i, j, k,gray;
        for(i=0;i<height;i++){
            for(j=0;j<width;j++){
                k = j + i * width;
                int imagedata_rgb = imagedata[k] & 0xff;
                gray = imagedata_rgb & mask[k];
                int r = gray;
                int g = gray;
                int b = gray;
                int rgb = (0xff << 24) | (r << 16) | (g << 8) | b ;
                imagedata[k] = rgb;
            }
        }
        return imagedata;
    }

    public int[] binaryImage(String filename) throws IOException {
        File file = new File(filename);
        BufferedImage src = ImageIO.read(file);
        imageThreshold imageThreshold = new imageThreshold();
        BufferedImage dest = new BufferedImage(src.getWidth(),src.getHeight(),src.getType());
        return (int[])imageThreshold.OTSUAlgorithm(src).get(0);
    }

    public int[] binaryImage(BufferedImage src) throws IOException {
        imageThreshold imageThreshold = new imageThreshold();
        BufferedImage dest = new BufferedImage(src.getWidth(),src.getHeight(),src.getType());
        return (int[])imageThreshold.OTSUAlgorithm(src).get(0);
    }

    public int[] binaryImage(int[] src) throws IOException {
        imageThreshold imageThreshold = new imageThreshold();
        return (int[])imageThreshold.OTSUAlgorithm(src,this.width,this.height).get(0);
    }


    public static void main(String[] args) throws IOException {
        SobelEdgeDetect test = new SobelEdgeDetect(25);//100
        String imageName = "/Users/yuchen/Desktop/49.9.png";
        String desImageName = "/Users/yuchen/Desktop/123sobelEdgeImage.jpg";
        String bry_imageName = "/Users/yuchen/Desktop/123.jpg";
        String test_image = "/Users/yuchen/Desktop/2.png";
        try {
            test.readImage(imageName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        test.createEdgeImage("123.jpg");



        int[] binary_imge = test.binaryImage(test.getNumberArea(test.binaryImage(test.createEdgeImage("123.jpg")),imageName));

        BufferedImage out = new BufferedImage(test.width,test.height,BufferedImage.TYPE_INT_RGB);

        for(int i = 0; i < test.height; i++)
        {
            for(int j = 0; j < test.width; j++)
            {
                out.setRGB(j,i,binary_imge[j+i*test.width]);
            }
        }

        test.writeImage(out,"1234567.jpg");

    }
}